/**************************************************************************** 
 * Copyright (c) 2011 ECAlford
 * Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 *****************************************************************************/

using System;
#if MicroFramework
using Microsoft.SPOT;
#endif
//using Microsoft.SPOT;

namespace AceBots.Xbee
{
    public enum XbeeFrameBufferOffsets : int
    {
        RFDataPacketOffset = 8, // this is the byte offset of the buffer to start a user RFDataPacket
        FrameIdOffsetOrSourceAddress = 4,
        ApiIdentityOffset = 3, // This is where ApiIdenty byte is
        DestinationAddress=5,

        
    }
    
    public class XbeeV1FrameMgr
    {
        private const byte cbStartByte = 0x7E;
        private const int ciDefaultMaxLen = 200;
        private const int ciParamIndex = 8;
        
        
        private int ciDataLength;
        private int ciBufferLength;
        private int ciPosition;
        private byte cbChecksum;
        private byte[] cbuffer;
        private long clTicksSenseLastReception;
        private int ciReceivedPacketLen;
        private short csSendAddress;
        

        #region Properities

        public int Position { get { return ciPosition; }
            set { ciPosition = value; }
        }
        public byte[] Buffer { get { return cbuffer; } }
        public bool IsComplete { get; set; }
    
        public int FullPacketLength
        {
            get { return ciDataLength+4;  } // ciDataLength + 1 byte for initial + 2 bytes for len + 1 byte for checksum
        }
        public int DataLength
        {
            get { return ciDataLength; }
            set { ciDataLength = value; }
        }
        
        public short SendAddress
        {
            get{return csSendAddress;}
            set
            {
                csSendAddress = value;
                if (ApiID == ApiIdentity.TransmitRequest16)
                {
                    cbuffer[(int)XbeeFrameBufferOffsets.DestinationAddress] = (byte)(csSendAddress >> 8);
                    cbuffer[(int)XbeeFrameBufferOffsets.DestinationAddress + 1] = (byte)(csSendAddress & 0xFF);
                }
            }

        }
        #endregion

        #region Class Lifetime
        public XbeeV1FrameMgr(int piBufferLen)
        {
            cbuffer = new byte[piBufferLen];
            Reset();
            ciDataLength = 0;
            ciBufferLength = piBufferLen;
        }
        public byte FrameID
        {
            get
            {
                if (ciBufferLength > 4) return cbuffer[3];
                else return 0;
            }
        }
        public int ReceivedPacketLength { get {return ciReceivedPacketLen;}}
        public ApiIdentity ApiID   {
            get
            { return (ApiIdentity)cbuffer[3];
            }
            set
            {
                cbuffer[3] = (byte)(value);
            }
        }
        public XBeeCommand  CurrentCommand()
        {
            int iVal=0;
            if (this.ApiID == Xbee.ApiIdentity.ATCommandResponse) 
            {
                iVal = cbuffer[5];
                iVal = (iVal << 8) + cbuffer[6];
            }
            return (XBeeCommand)iVal;
        }                                    

        #endregion

        #region Public Methods
        public void Reset()
        {  // resets the buffer position and Checksum and clears the buffer
            Position = 0;
            cbChecksum = 0;
            IsComplete = false;
            for (int i = 0; i < ciBufferLength; ++i)
            {
                cbuffer[i] = 0;
            }
        }

        public void Append(byte pbNewByte)
        {
            long lTicks = DateTime.Now.Ticks;
            if (pbNewByte == cbStartByte && Position != 0)
            {
                // Check to see if we have been waiting a while If so then this is probably
                // a resend so reset position and checksum
                TimeSpan span = new TimeSpan(lTicks - clTicksSenseLastReception);
                if (span.Seconds > 1)
                {
                    // Timeout!
                    Reset();
                }
            }
            clTicksSenseLastReception = lTicks;
            // put the byte received in the buffer only 
            cbuffer[ciPosition] = pbNewByte;
            // Depending on Position Set the buffer
            switch (ciPosition)
            {
                case 0: // Start Delimitor
                    if (pbNewByte != cbStartByte)
                    {
                        ciPosition = -1;// This is an error so essentially reset (ciPosition incremented
                        // after end of this switch
                    }
                    ciReceivedPacketLen = 0;
                    break;
                case 1: // MSB Length

                    ciReceivedPacketLen = pbNewByte << 8;

                    break;
                case 2: // LSB Length
                    ciReceivedPacketLen |= pbNewByte;
                    if (ciReceivedPacketLen > ciBufferLength - 4)
                    {
                        // this is trouble the packet is too big so we are going to reset
                        Reset();
                        ciPosition = -1; // to handle the increment after switch statement
                    }

                    break;
                default: // Frame data
                    cbChecksum += pbNewByte;
                    if (ciPosition == 3 + ciReceivedPacketLen)
                    {
                        if (cbChecksum == 0xFF)
                        {
                            IsComplete = true;
                            DataLength = ciReceivedPacketLen;
                            break;
                        }
                        else
                        {
                            Reset();
                            ciPosition = -1;
                        }
                    }


                    break;
            }
            ++ciPosition; // increment position


        }

        public byte CalculateChecksum()
        {

            // This routine calculates the checksum for the current buffer
            // for this to work ciDataLength must be set correctly.
            byte bChecksum = 0xFF;
            for (int i = 0; i < ciDataLength; i++)
            {
                bChecksum -= cbuffer[i + 3];
            }
            return bChecksum;
        }



        #endregion

        #region Command Parameter Parsing
        public ushort UShortCommandParameter()
        {
            int iRetVal=0;
            if (ciReceivedPacketLen+4 >= 9)
            {
                iRetVal = cbuffer[ciParamIndex];
                iRetVal = (iRetVal << 8) + cbuffer[ciParamIndex+1];
            }
            return (ushort)iRetVal;

        }

        public string StringCommandParameter()
        {
            string str = string.Empty;
            for (int i = ciParamIndex; i < ciReceivedPacketLen+3 && cbuffer[i] != 0; i++)
            {
                str += (char)cbuffer[i];
            }
            return str;
        }

        public uint UintCommandParameter()
        {
            int iRetVal = 0;
            if (ciReceivedPacketLen + 4 >=11)
            {
                iRetVal =  cbuffer[ciParamIndex];
                iRetVal = (iRetVal << 8) + cbuffer[ciParamIndex + 1];
                iRetVal = (iRetVal << 8) + cbuffer[ciParamIndex + 2];
                iRetVal = (iRetVal << 8) + cbuffer[ciParamIndex + 3];
            }
            return (uint)iRetVal;

        }

        public int AvailableBufferLen()
        {
            // this actually returns the max length of the buffer -1 because
            // the last byte of the buffer is reserved for the checksum
            return (cbuffer.Length - 1);
        }


        #endregion

        #region FillXbeePacket Methods
        public void FillXbeeDataPacket(byte pbFrameid,int piDataLength)
        {
            ciDataLength = piDataLength;
            cbuffer[0] = cbStartByte;
            cbuffer[1] = 0; //Msb of the packet size
            cbuffer[2] = (byte)piDataLength; //Msb of the packet size
            cbuffer[(int)AceBots.Xbee.XbeeFrameBufferOffsets.FrameIdOffsetOrSourceAddress] = pbFrameid;
            cbuffer[piDataLength + 3] = CalculateChecksum();
            
        }
        public void FillXbeeCommandPacket(AceBots.Xbee.XBeeCommand pCommand, byte pbPacketID)
        {
            // This FillXbeeCommandPacket method is used to create a query packet for a command
            FillPacketBeginning(AceBots.Xbee.ApiIdentity.ATCommand, 4, pbPacketID);
            cbuffer[5] = (byte)(((ushort)pCommand) >> 8); // First byte of command
            cbuffer[6] = (byte)(((ushort)pCommand) & 0xFF); // Second byte of command
            cbuffer[7] = CalculateChecksum();


        }
        public void FillXbeeCommandPacket(AceBots.Xbee.XBeeCommand pCommand, byte pbPacketID, ushort piValue)
        {
            // This FillXbeeCommandPacket method is used to create a query packet for a command
            FillPacketBeginning(AceBots.Xbee.ApiIdentity.ATCommand, 6, pbPacketID);
            cbuffer[5] = (byte)(((ushort)pCommand) >> 8); // First byte of command
            cbuffer[6] = (byte)(((ushort)pCommand) & 0xFF); // Second byte of command
            cbuffer[7] = (byte)(piValue >> 8);
            cbuffer[8] = (byte)(piValue & 0xFF);
              cbuffer[9] = CalculateChecksum();


        }
        private void FillPacketBeginning(AceBots.Xbee.ApiIdentity pIdentity, byte pbLength, byte pbPacketID)
        {
            // This gets used a lot so put in a single routine
            Reset();
            cbuffer[0] = cbStartByte;
            cbuffer[1] = 0; //Msb of the packet size
            cbuffer[2] = pbLength;
            cbuffer[3] = (byte)pIdentity;
            cbuffer[(int)AceBots.Xbee.XbeeFrameBufferOffsets.FrameIdOffsetOrSourceAddress] = pbPacketID;
            ciDataLength = pbLength;
         
        }
        #endregion
    }
}
